package main

import (
	"os"
	"strings"
	"strconv"
	"go/ast"
	"go/token"
	"go/parser"
)

type Contract struct {
	str string
	exp ast.Expr
}

type Contracts struct {
	pre, in, post, pan []Contract
}

func (c *Contracts) Len() int {
	return len(c.pre) + len(c.in) + len(c.post) + len(c.pan)
}

func contracts(fname string, funcs []*JunkDrawer) (cf []*JunkDrawer, err os.Error) {
	parser := newParser(fname)
	for _, fn := range funcs {
		if len(fn.doc) == 0 {
			continue
		}
		parser.Reset(fn.doc)
		if err = parser.parse_contracts(); err != nil {
			return nil, err
		}
		if parser.Contracts.Len() > 0 {
			fn.cons = parser.Contracts
			cf = append(cf, fn)
		}
	}
	return
}

type cparser struct {
	fname    string
	fset     *token.FileSet
	line     int
	comments []string
	*Contracts
}

func newParser(fname string) *cparser {
	return &cparser{
		fname:     fname,
		fset:      token.NewFileSet(),
		Contracts: &Contracts{},
	}
}

func (p *cparser) Reset(comments []string) {
	p.line = 0
	p.comments = comments
	p.Contracts = &Contracts{}
}

func (p *cparser) Line() string {
	return strings.TrimSpace(p.comments[p.line])
}

func (p *cparser) parse_contracts() (err os.Error) {
	var c []Contract
	for ; p.line < len(p.comments); p.line++ {
		comment := p.Line()
		is := func(s string) bool { return strings.HasPrefix(comment, s+SEP) }
		if is(PRE) {
			c, err = p.parse_contract_block()
			if err != nil {
				return
			}
			p.pre = append(p.pre, c...)
		}
		if is(POST) {
			c, err = p.parse_contract_block()
			if err != nil {
				return
			}
			p.post = append(p.post, c...)
		}
		if is(IN) {
			c, err = p.parse_contract_block()
			if err != nil {
				return
			}
			p.in = append(p.in, c...)
		}
		if is(PANIC) {
			c, err = p.parse_contract_block()
			if err != nil {
				return
			}
			p.pan = append(p.pan, c...)
		}
	}
	return
}

//TODO we should always require a blank comment to terminate a block so that
//they are properly formatted by godoc and co
func (p *cparser) parse_contract_block() (exp []Contract, err os.Error) {
	comment := p.Line() //we still have (type)+SEP as prefix
	comment = strings.SplitN(comment, SEP, 2)[1]
	var e Contract
	if strings.TrimSpace(comment) != "" {
		e, err = p.expr(comment)
		if err != nil {
			return
		}
		return []Contract{e}, nil
	}
	for p.line++; p.line < len(p.comments); p.line++ {
		comment = p.Line()
		if comment == "" {
			p.line++
			return
		}
		e, err = p.expr(comment)
		if err != nil {
			exp = nil
			return
		}
		exp = append(exp, e)
	}
	return
}

func (p *cparser) expr(e string) (Contract, os.Error) {
	exp, err := parser.ParseExpr(p.fset, p.fname, e)
	if _, ok := exp.(*ast.BadExpr); ok || err != nil {
		return Contract{}, os.NewError(err.String() + ", in contract «" + e + "»")
	}
	return Contract{Quote(e), exp}, nil
}

func Quote(s string) string {
	return strconv.Quote(strings.TrimSpace(s))
}
